package com.hxkj.client.service.impl;

import com.google.common.collect.Maps;
import com.hxkj.client.service.LabelDirectiveService;
import com.hxkj.client.service.ManuscriptServiceClient;
import com.hxkj.client.service.WebsiteAddrServiceClient;
import com.hxkj.client.service.taglib.config.LableConstant;
import com.hxkj.domain.Constant.CmsConstants;
import com.hxkj.domain.context.EnvironmentContextHolder;
import com.hxkj.domain.enums.ResouresTypeEnum;
import com.hxkj.domain.enums.TemplteExceptionEnum;
import com.hxkj.domain.po.*;
import com.hxkj.utils.MD5Utils;
import com.integral.mall.file.po.SysFile;
import com.integral.mall.file.service.SysFileService;
import com.platform.common.domain.exception.CustomException;
import com.platform.common.domain.exception.CustomMessageException;
import com.platform.common.domain.result.ResultDataStruct;
import com.platform.common.util.DateUtils;
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;

import javax.annotation.Resource;
import java.io.StringWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.hxkj.domain.Constant.CmsConstants.*;

/***
 * 自定义标签封装类
 * @author yaosy
 * @date 2018年9月07日12:02:16
 * ***/
@Service
@Slf4j
public class LabelDirectiveServiceImpl extends AbstarctLabelService implements LabelDirectiveService {

    private static final String WAP_ROOT = "wap";

    @Autowired
    private WebsiteAddrServiceClient websiteAddrServiceClient;

    @Autowired
    private SysFileService sysFileService;

    @Autowired
    private ManuscriptServiceClient manuscriptServiceClient;

    @Autowired
    private FreeMarkerConfigurer freeMarkerConfigurer;

    @Autowired
    private RedisTemplate redisTemplate;

    @Value("${storage.sftp.fileRoot}")
    private String fileRoot;

    @Value("${cms.tmpRes}")
    private String tmpRes;

    @Value("${cms.domainAccess}")
    private boolean domainAccess;

    StringTemplateLoader templateLoader = new StringTemplateLoader();


    /**
     * 模板可视化与静态文件生成共用方法
     * 模板可视化时生成本地临时静态文件
     * 静态文件生成时上传静态文件到静态网站
     * @param freeMarker
     * @return
     * @throws Exception
     */
    @Override
    public SysFile labelDirective(FreeMarker freeMarker) throws Exception {
        SysFile sysFile = null;
        this.checkDirective(freeMarker);
        String userToken = "";
        if(!StringUtils.isEmpty(freeMarker.getNote())) {
            userToken = freeMarker.getUserToken() + "_" + MD5Utils.getMd5ByTemplte();
            freeMarker.setUserToken(userToken);
        }
        //获取静态网站规划
        WebsiteAddr websiteAddr = this.getWebSiteAddr(freeMarker);
        if (StringUtils.isNotBlank(freeMarker.getNote())) {
            // 模板可是化
            sysFile = this.visualLabelDirective(freeMarker,websiteAddr);
        }else{
            // 生成pc端静态文件
            if(freeMarker.getTempletId() !=null) {
                this.pcLabelDirective(freeMarker,websiteAddr);
            }
            // 生成wap端静态文件
            if(freeMarker.getWapTempletId() !=null) {
                this.wapLabelDirective(freeMarker,websiteAddr);
            }
        }
        if(!StringUtils.isEmpty(userToken)) {
            redisTemplate.delete(userToken);
        }
        return sysFile;
    }

    /**
     * 模板可视化标签解析
     * @param freeMarker
     * @param websiteAddr
     * @return
     * @throws Exception
     */
    private SysFile visualLabelDirective(FreeMarker freeMarker, WebsiteAddr websiteAddr) throws Exception{
        SysFile sysFile = null;
        //可视化时不管是pc还是wap,模板参数都是通过templetId传进来的
        Templet templet = this.getTemplet(freeMarker.getSiteId(),freeMarker.getTempletId());
        if (templet == null) {
            throw new CustomException(TemplteExceptionEnum.TEMPLTE_NOT_EXISTS_MSG);
        }
        sysFile =  this.buildVisualizationFile(freeMarker, templet, websiteAddr);
        // 可视化时只根据选择可视化模板生成一个临时静态文件,需要返回文件路径
        return sysFile;
    }

    /**
     * pc端静态文件生成
     * @param freeMarker
     * @param websiteAddr
     * @return
     * @throws Exception
     */
    private SysFile pcLabelDirective(FreeMarker freeMarker, WebsiteAddr websiteAddr) throws Exception{
        SysFile sysFile = null;
        // 设置模板解析环境
        this.setEnvironment(freeMarker, websiteAddr, true);
        if(freeMarker.getTempletId() !=null ){
            //生成PC静态文件
            Templet pcTemplet = this.getTemplet(freeMarker.getSiteId(),freeMarker.getTempletId());
            if (pcTemplet == null) {
                throw new CustomException(TemplteExceptionEnum.TEMPLTE_NOT_EXISTS_MSG);
            }
            sysFile = this.buildStaticFile(freeMarker,pcTemplet,websiteAddr,true);
            log.debug("上传PC端静态文件到服务器,path:{},filename:{}",sysFile.getFilePath(),sysFile.getFileName());
        }
        return sysFile;
    }

    /**
     * wap端静态文件生成
     * @param freeMarker
     * @param websiteAddr
     * @return
     * @throws Exception
     */
    private SysFile wapLabelDirective(FreeMarker freeMarker, WebsiteAddr websiteAddr) throws Exception{
        SysFile sysFile = null;
        // 设置模板解析环境
        this.setEnvironment(freeMarker, websiteAddr,false);
        // 生成wap端静态文件
        Templet wapTemplet = this.getTemplet(freeMarker.getSiteId(),freeMarker.getWapTempletId());
        if (wapTemplet == null) {
            throw new CustomException(TemplteExceptionEnum.TEMPLTE_NOT_EXISTS_MSG);
        }
        sysFile = this.buildStaticFile(freeMarker, wapTemplet,websiteAddr,false);
        log.debug("上传Wap端多页静态文件第1页到服务器,path:{},filename:{}",sysFile.getFilePath(),sysFile.getFileName());
        return sysFile;
    }

    /**
     * 生成可视化临时静态文件
     * @param freeMarker 前端入参
     * @param htmlTemplet 静态页模板
     * @param websiteAddr 网站信息
     * @return
     * @throws Exception
     */
    private SysFile buildVisualizationFile(FreeMarker freeMarker, Templet htmlTemplet, WebsiteAddr websiteAddr) throws Exception{
        Configuration cfg = null;
        StringWriter stringWriter = null;
        SysFile sysFile = null;
        try {
            // 下载模板到本地临时目录
            String source = download(htmlTemplet, freeMarker);
            // 设置本次解析所需要用到的环境变量
            cfg = freeMarkerConfigurer.getConfiguration();
            Map root = getEnviroment(freeMarker);
            templateLoader.putTemplate(htmlTemplet.getTempletName(), source);
            cfg.setTemplateLoader(templateLoader);
            Template temp = cfg.getTemplate(htmlTemplet.getTempletName(), "utf-8");
            stringWriter = new StringWriter();
            temp.process(root, stringWriter);
            stringWriter.flush();
            //本地临时文件
            sysFile = new SysFile();
            sysFile.setFilePath(stringWriter.toString());
            stringWriter.flush();
            temp.getAutoFlush();
        } catch (Exception e) {
            log.error("生成模板可视化临时文件出错",e);
            throw new CustomException(TemplteExceptionEnum.TEMPLTE_PARSING_DATA_FAILED);
        } finally {
            if (stringWriter != null) stringWriter.close();
        }
        return sysFile;
    }

    /**
     * 生成静态文件并上传到静态网站
     * @param freeMarker 前端入参
     * @param htmlTemplet 静态页模板
     * @param websiteAddr 网站信息
     * @param isPcClient 是否PC端
     * @return
     * @throws Exception
     */
    private SysFile buildStaticFile(FreeMarker freeMarker, Templet htmlTemplet, WebsiteAddr websiteAddr, boolean isPcClient) throws Exception{
        StringWriter stringWriter = null;
        SysFile sysFile = null;
        try {
            // 下载模板到本地临时目录
            String source = download(htmlTemplet, freeMarker);
            templateLoader.putTemplate(htmlTemplet.getTempletName(), source);
            // 设置本次解析所需要用到的环境变量
            Configuration cfg = freeMarkerConfigurer.getConfiguration();
            cfg.setTemplateLoader(templateLoader);
            Template temp = cfg.getTemplate(htmlTemplet.getTempletName(), "utf-8");
            stringWriter = new StringWriter();
            Map root = this.getEnviroment(freeMarker);
            temp.process(root, stringWriter);
            stringWriter.flush();
            //上传静态文件到静态网站
            sysFile = this.upload(freeMarker, websiteAddr, stringWriter.toString(),isPcClient);
            stringWriter.flush();
            temp.getAutoFlush();
        } catch (CustomException | CustomMessageException e) {
            throw e;
        }catch (Exception e) {
            log.error("解析模板生成静态文件出错",e);
            throw new CustomException(TemplteExceptionEnum.TEMPLTE_PARSING_DATA_FAILED);
        } finally {
            if (stringWriter != null) stringWriter.close();
        }
        return sysFile;
    }


    /**
     * freemarker校验
     *
     * @param freeMarker
     * @throws Exception
     */
    protected void checkDirective(FreeMarker freeMarker) throws Exception {
        if (freeMarker == null) {
            throw new CustomException(TemplteExceptionEnum.TEMPLTE_DATA_BLANK);
        }
        if (freeMarker.getTempletId() == null && freeMarker.getWapTempletId() == null) {
            throw new CustomException(TemplteExceptionEnum.TEMPLTE_NOT_ID);
        }
        if (freeMarker.getSiteId() == null) {
            throw new CustomException(TemplteExceptionEnum.TEMPLTE_NOT_SITEID);
        }
        if (freeMarker.getChannelId() == null) {
            throw new CustomException(TemplteExceptionEnum.TEMPLTE_NOT_CHANNL_ID);
        }
    }


    /**
     * 上传静态文件到静态站点
     * @param freeMarker
     * @param websiteAddr
     * @param content
     * @param isPcClient
     * @return
     * @throws Exception
     */
    protected SysFile upload(FreeMarker freeMarker, WebsiteAddr websiteAddr, String content, boolean isPcClient) throws Exception {
        // 不同客户端目录前缀, PC端为空, WAP端为"wap/"
        String clientPath = isPcClient ? "" : WAP_ROOT+"/";
        //静态网址规划,前缀
        String siteCtxPath = websiteAddr.getUrlPrefix();
        if(StringUtils.isBlank(siteCtxPath)){
            throw new CustomMessageException(400,"站点目录不能为空");
        }
        Column column =  this.getColumn(freeMarker);
        String filename = "";
        //静态网址规划,目录规则,默认按年月日目录保存
        long time = freeMarker.getTime() == null ? System.currentTimeMillis() : freeMarker.getTime();
        String urlRegularPath = DateUtils.toDateToString(new Date(time), websiteAddr.getUrlRegular() == null ? "yyyyMMdd" : websiteAddr.getUrlRegular());
        String uploadPath = siteCtxPath +"/" +  urlRegularPath+ "/"; //默认设置上传目录站点目录+创建日期目录,非此规则单独设置覆盖
        String saveShortUrlPath = "/" +  urlRegularPath; //默认设置url前缀为创建日期目录,非此规则单独设置覆盖
        if (freeMarker.getStatus() == CmsConstants.CHANNEL) {// 频道页
            // 频道首页
            if (freeMarker.getAliasName().equals("index")) {
                filename = "index";
                uploadPath = siteCtxPath;  //频道静态页上传目录为站点目录根下
                saveShortUrlPath = "";  //首页不
            } else {
                // 其他频道页
                filename = freeMarker.getStatus() + "" + freeMarker.getSiteId() + freeMarker.getChannelId();
            }
        }else if (freeMarker.getStatus() == COLUMNLIST) {// 分类列表
            log.debug("当前分类的名称{}",freeMarker.getPageNum());
            uploadPath = siteCtxPath+"/"+column.getAlias(); //分类静态页上传目录为站点目录+分类别名
            saveShortUrlPath = "/"+column.getAlias();
            if(freeMarker.getPageNum()==1) {
                filename = "index";
            }else {
                filename = freeMarker.getPageNum() + "";
            }
        }else if (freeMarker.getStatus() == ARTICLEDETAIL) {// 文章
            filename = freeMarker.getStatus() + "" + freeMarker.getSiteId() + freeMarker.getArticleId() + "";
        } else if (freeMarker.getStatus() == LEAFLET) {// 单页
            filename = freeMarker.getStatus() + "" + freeMarker.getSiteId() + freeMarker.getLeafletId();
        } else if (freeMarker.getStatus() == TAGSDEATIL) {// tag详情列表
            filename = freeMarker.getStatus() + "" + freeMarker.getSiteId() + freeMarker.getTagsId() + "_" + freeMarker.getPageNum();
        }
        Manuscript manuscript = new Manuscript();
        manuscript.setSiteId(freeMarker.getSiteId());
        manuscript.setStatus(freeMarker.getStatus());
        manuscript.setChannelId(freeMarker.getAliasName());
        manuscript.setAlisName(filename); //这里存文件名
        Manuscript remoteManuscript = this.getMabuscript(manuscript);
        boolean isUpload = false;  //是否是更新,如果静态文件已经存在,就是更新
        if (remoteManuscript != null) {
            //不是第一次生成静态文件时上传路径使用原来的路径,
            manuscript = remoteManuscript;
            isUpload = true;
        }

        log.debug("更新上传目录为{}", clientPath + uploadPath);
        // 有静态资源则不需要新增静态地址，更新其内容即可
        SysFile sysFile = sysFileService.upload(filename, ResouresTypeEnum.HTML.getType(), clientPath + uploadPath, IOUtils.toInputStream(content, "UTF-8"));
        log.debug("上传filePath={}", sysFile.getFilePath());
        // db中文章路径按PC端规则保存,Wap端通过加wap前缀目录来获取wap端的静态文件,所以只需要生成pc端保存一下文件路径即可
        if(isPcClient){
            manuscript.setUrlPath(saveShortUrlPath +"/"+filename+"."+ResouresTypeEnum.HTML.getType());
            // 保存静态文件路径
            this.saveOrUpdateManuscript(manuscript, freeMarker, isUpload);
        }
        return sysFile;
    }

    /**
     * 判断静态文件是否已经存在
     *
     * @param manuscript
     * @return
     * @throws Exception
     */
    protected Manuscript getMabuscript(Manuscript manuscript) throws Exception {
        ResultDataStruct<Manuscript> resultStruct = manuscriptServiceClient.getManuscript(manuscript);
        if(resultStruct.getCode() != 200){
            throw new CustomMessageException(resultStruct.getCode(),resultStruct.getMessage());
        }
        return resultStruct.getData();
    }


    /**
     * 下载模板到临时目录
     *
     * @param
     * @param templetData
     * @param freeMarker
     * @throws Exception
     */
    protected String download(Templet templetData, FreeMarker freeMarker) throws Exception {
        String sourceData = templetData.getSource();
        if (!StringUtils.isEmpty(freeMarker.getNote()))
            sourceData = replaceContent(sourceData, freeMarker);
        String sources = getUrl(sourceData, freeMarker);
        return sources;
    }


    /**
     * 获取url
     *
     * @param content
     * @param freeMarker
     * @return
     * @throws Exception
     */
    protected String getUrl(String content, FreeMarker freeMarker) throws Exception {
        Pattern p = Pattern.compile("<(S*#include?)[^>]*>.*?|<.html?/> ");
        Matcher m = p.matcher(content);
        String finalContent;
        while (m.find()) {
            String name = m.group();
            String contentname = m.group();
            if (name.indexOf("tabs") != -1) {
                Pattern p2 = Pattern.compile("tabs=['|\"].+['|\"]{0}>$");//
                Matcher m2 = p2.matcher(contentname);
                String tabName = "";
                while (m2.find()) {
                    tabName = m2.group();
                }
                if (tabName.length() > 0)
                    name = name.substring(name.indexOf("\"") + 1, name.indexOf(tabName) - 2);
            } else {
                name = name.substring(name.indexOf("\"") + 1, name.length() - 2);
            }
            if (FilenameUtils.getExtension(name).equals("html")) {
                Templet t = new Templet();
                t.setPath(name);
                t.setSiteId(freeMarker.getSiteId());
                Templet templet1 = getTemplteContent(t);
                if (templet1 != null) {
                    finalContent = contentname.replace(contentname, templet1.getSource());
                    if (!StringUtils.isEmpty(freeMarker.getNote())) {
                        finalContent = explain(finalContent, templet1.getTempletId());
                    }
                    content = content.replace(contentname, finalContent);
                } else {
                    //throw new CustomMessageException(402, "引入[" + name + "]路径不正确!");
                    log.error("引入[{}]路径不正确", name);
                }
            }
            String sources = m.group();
            Pattern p2 = Pattern.compile("tabs=['|\"].+['|\"]{0}>$");//
            Matcher m2 = p2.matcher(sources);
            while (m2.find()) {
                String tabName = m2.group();
                log.debug("include标签为{}", tabName);
                sources = sources.replace(tabName, ">");
                log.debug("替换后的内容为{}", sources);
            }
        }
        return content;
    }


    /**
     * 解析include模板中的标签并放入模板中
     *
     * @param content
     * @param templteId
     * @return
     */
    protected String explain(String content, Long templteId) {
        Pattern pa3 = Pattern.compile("<@[^link][^script][^>]+>");
        Matcher m3 = pa3.matcher(content);
        String finalContent;
        while (m3.find()) {
            String taglib = m3.group();
            // 适用于可视化
            finalContent = taglib.replace(">", " templetId=" + templteId + ">");
            content = content.replace(taglib, finalContent);
        }
        return content;
    }

    /**
     * 替换include标签 可视化ca
     *
     * @param content
     * @param freeMarker
     * @return
     */
    protected String replaceContent(String content, FreeMarker freeMarker) {
        Pattern p = Pattern.compile("<(S*#include?)[^>]*>.*?|<.html?/> ");//
        Matcher m = p.matcher(content);
        StringBuffer sb;
        String finalContent;
        while (m.find()) {
            sb = new StringBuffer();
            String name = m.group();
            String contentname = m.group();
            String tabs = "";
            if (name.indexOf("tabs") != -1) {
                name = name.substring(name.indexOf("\"") + 1, name.indexOf("tabs") - 2);
            } else {
                name = name.substring(name.indexOf("\"") + 1, name.length() - 2);
            }
            if (FilenameUtils.getExtension(name).equals("html")) {
                Integer num = redisTemplate.opsForValue().get(freeMarker.getUserToken()) == null ? 0 : (Integer) redisTemplate.opsForValue().get(freeMarker.getUserToken());
                num++;
                if (StringUtils.isEmpty(tabs)) {
                    sb.append("<!--_").append(LableConstant.CMS_HXKJTAGS).append("_cms_include num:").append(num).append("|cur:").append(name).append("|tabs:").append(name).append("|").append("-->");
                } else {
                    tabs = tabs.replace("=", ":").replace("\"", "");
                    sb.append("<!--_").append(LableConstant.CMS_HXKJTAGS).append("_cms_include num:").append(num).append("|cur:").append(name).append("|").append(tabs).append("|").append("-->");
                }
                sb.append(contentname);
                finalContent = contentname.replace(contentname, sb.toString());
                content = content.replace(contentname, finalContent);
                redisTemplate.opsForValue().set(freeMarker.getUserToken(), num);
            }
        }
        return content;
    }


    /***
     * 异步处理插入稿件表
     * **/
    protected void saveOrUpdateManuscript(Manuscript manuscript, FreeMarker freeMarker, boolean isUpdate) throws Exception {
        if (freeMarker.getStatus() == COLUMNLIST){
            if(freeMarker.getPageNum() == 1) {
                //更新静态文件路径到对应的数据表中
                this.saveManuscriptPath(FilenameUtils.getPath(manuscript.getUrlPath()), freeMarker);
            }
        } else {
            //更新静态文件路径到对应的数据表中
            this.saveManuscriptPath(manuscript.getUrlPath(), freeMarker);
        }
        if(!isUpdate) {
            // 每个站点下,只会有一条静态文件地址数据
            Integer maxSeqNum = manuscriptServiceClient.getMaxSeqNum();
            manuscript.setSeqNum(maxSeqNum);
            manuscript.setCreateor(1L);
            manuscript.setCreateStatus(0);
            manuscript.setCreateTime(System.currentTimeMillis());
            manuscript.setModifyBy(1L);
        }
        manuscript.setLastModifiedDate(System.currentTimeMillis());
        manuscriptServiceClient.saveOrUpdate(manuscript);
    }

    /***
     * 设置本次模板解析的环境变量
     *
     * @param freeMarker
     * @param websiteAddr
     * @param isPcClient 是否生成pc端静态时环境
     * @throws Exception**/
    public void setEnvironment(FreeMarker freeMarker, WebsiteAddr websiteAddr, boolean isPcClient) throws Exception {
        Map<String, Object> enviroment = EnvironmentContextHolder.getEnvironment();
        enviroment.put("siteId", freeMarker.getSiteId());
        enviroment.put("columnId", freeMarker.getColumnId());
        enviroment.put("tagsId", freeMarker.getTagsId());
        enviroment.put("articleId", freeMarker.getArticleId());
        enviroment.put("pageNum", freeMarker.getPageNum());
        enviroment.put("sitename", websiteAddr == null ? "" : websiteAddr.getUrlPrefix());
        enviroment.put("urlPrefix", websiteAddr == null ? "" : websiteAddr.getUrlPrefix());
        enviroment.put("note", freeMarker.getNote());
        enviroment.put("userToken", freeMarker.getUserToken());
        enviroment.put("isPcClient", isPcClient);
        Map<Long,String> siteCtxMap = new HashMap<>();
        List<WebsiteAddr> list = websiteAddrServiceClient.getList(new WebsiteAddr());
        if(list!=null){
            //TODO 路径中加upload是因为nginx没有做目录映射,生成的链接路径无法访问,临时修改,静态文件需要上传到upload目录下
            //wap端如果没有做域名映射,地址前面需要拼接上/wap目录
            String wapPath = this.domainAccess ? "/upload" : "/upload/" + WAP_ROOT;
            for(WebsiteAddr item : list){
                if(isPcClient) {
                    // 如果没有做域名映射,pc端地址需要拼接上站点二级目录
                    siteCtxMap.put(item.getSiteId(), this.domainAccess ? "/upload" : "/upload/" + item.getUrlPrefix());
                }else {
                    //wap端必须拼接站点二级目录
                    siteCtxMap.put(item.getSiteId(), wapPath + "/" + item.getUrlPrefix());
                }
            }
        }
        enviroment.put("siteCtxMap", siteCtxMap);
    }


    public Map<String, Object> getEnviroment(FreeMarker freeMarker) throws Exception {
        ConcurrentMap<String, Object> concurrentMap = Maps.newConcurrentMap();
        if (freeMarker.getArticleId() != null) {
            concurrentMap.put("article", getArticle(freeMarker));
        }
        if (freeMarker.getLeafletId() != null) {
            concurrentMap.put("leaflet", getLeaflet(freeMarker));
        }
        if (freeMarker.getColumnId() != null) {
            Column column = getColumn(freeMarker);
            concurrentMap.put("column", column == null ? "" : column);
        }
        if (freeMarker.getTagsId() != null) {
            Tags tags = getTags(freeMarker);
            concurrentMap.put("tag", tags == null ? "" : tags);
        }
        concurrentMap.put("siteUniversal", getSiteUniversal(freeMarker));
        return concurrentMap;
    }
}
